package cn.kimming.rpc.remoting.spring;

import cn.kimming.rpc.common.annotation.RpcReference;
import cn.kimming.rpc.common.constant.LoadBalance;
import cn.kimming.rpc.common.constant.ProxyWay;
import cn.kimming.rpc.common.constant.RpcConstant;
import cn.kimming.rpc.common.constant.SerializationWay;
import cn.kimming.rpc.remoting.client.RpcClientConfig;
import cn.kimming.rpc.remoting.proxy.RpcProxyFactory;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.config.BeanPostProcessor;
import org.springframework.context.EnvironmentAware;
import org.springframework.core.env.Environment;
import org.springframework.util.StringUtils;

import java.lang.reflect.Field;

public class ReferenceBeanPostProcessor implements BeanPostProcessor, EnvironmentAware {

    private static final Logger logger = LoggerFactory.getLogger(ReferenceBeanPostProcessor.class);

    private Environment environment;

    @Override
    public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
        return BeanPostProcessor.super.postProcessBeforeInitialization(bean, beanName);
    }

    @Override
    public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
        Class<?> beanClass = bean.getClass();
        Field[] declaredFields = beanClass.getDeclaredFields();
        try {
            for (Field field : declaredFields) {
                // 判断是否有标注RpcReference注解
                RpcReference reference = field.getDeclaredAnnotation(RpcReference.class);
                if (reference == null) {
                    continue;
                }

                // 判断是否为接口
                Class<?> fieldType = field.getType();
                if (!fieldType.isInterface()) {
                    throw new RuntimeException(String.format("{0}'s field {1}'s type must be a interface because it was annotated by @RpcReference", beanName, field.getName()));
                }


                // 根据注解构建客户端代理配置
                RpcClientConfig config = buildConfig(reference);

                // 创建代理
                Object proxy = RpcProxyFactory.createClient(fieldType, config);
                field.setAccessible(true);
                field.set(bean, proxy);
            }
        } catch (Exception e) {
            logger.error("create rpc proxy failed", e);
        }

        return bean;
    }

    private RpcClientConfig buildConfig(RpcReference reference) {
        String registryUrl = StringUtils.hasText(reference.registryUrl()) ?
                reference.registryUrl() :
                environment.getProperty(RpcConstant.MY_RPC_ZK_URL, RpcConstant.MY_RPC_ZK_URL_DEFAULT_VALUE);
        long timeout = reference.timeout() >= 0 ?
                reference.timeout() :
                environment.getProperty(RpcConstant.MY_RPC_CALL_TIMEOUT_MS, Long.class, RpcConstant.MY_RPC_CALL_TIMEOUT_MS_DEFAULT_VALUE);
        ProxyWay proxyWay = reference.proxyWay() != ProxyWay.NONE ?
                reference.proxyWay() :
                environment.getProperty(RpcConstant.MY_RPC_CLIENT_PROXY_WAY, ProxyWay.class, RpcConstant.MY_RPC_CLIENT_PROXY_WAY_DEFAULT_VALUE);
        SerializationWay serializationWay = reference.serializationWay() != SerializationWay.NONE ?
                reference.serializationWay() :
                environment.getProperty(RpcConstant.MY_RPC_SERIALIZATION_WAY, SerializationWay.class, RpcConstant.MY_RPC_SERIALIZATION_WAY_DEFAULT_VALUE);
        LoadBalance loadBalance = reference.loadBalance() != LoadBalance.NONE ?
                reference.loadBalance() :
                environment.getProperty(RpcConstant.MY_RPC_LOAD_BALANCE, LoadBalance.class, RpcConstant.MY_RPC_LOAD_BALANCE_DEFAULT_VALUE);

        RpcClientConfig config = RpcClientConfig.configurer()
                .ip(reference.ip())
                .port(reference.port())
                .registryUrl(registryUrl)
                .timeout(timeout)
                .proxyWay(proxyWay)
                .serializationWay(serializationWay)
                .loadBalance(loadBalance)
                .config();
        return config;
    }

    @Override
    public void setEnvironment(Environment environment) {
        this.environment = environment;
    }
}
